﻿using System.Diagnostics;
using System.IO.Ports;

namespace Yewan.Modbus
{
    public abstract class ModbusSerial : BaseModbus
    {
        private readonly SerialPort serialPort;
        private readonly CancellationTokenSource cts = new();
        private readonly static object serialLock = new();

        /// <summary>
        /// 端口名称
        /// </summary>
        public string PortName { get; set; } = "COM1";

        /// <summary>
        /// 波特率
        /// </summary>
        public int BaudRate { get; set; } = 9600;

        /// <summary>
        /// 校验方式
        /// </summary>
        public Parity Parity { get; set; } = Parity.None;

        /// <summary>
        /// 数据位
        /// </summary>
        public int DataBits { get; set; } = 8;

        /// <summary>
        /// 停止位
        /// </summary>
        public StopBits StopBits { get; set; } = StopBits.One;

        /// <summary>
        /// 读取超时时间
        /// </summary>
        public int ReadTimeout { get; set; } = 2000;

        /// <summary>
        /// 读取缓冲区大小
        /// </summary>
        public int ReadBufferSize { get; set; } = 4096;

        /// <summary>
        /// 是否打开
        /// </summary>
        public bool IsOpen => serialPort.IsOpen;

        protected ModbusSerial()
        {
            serialPort = new SerialPort();
        }

        public override void Connect()
        {
            serialPort.PortName = PortName;
            serialPort.BaudRate = BaudRate;
            serialPort.DataBits = DataBits;
            serialPort.Parity = Parity;
            serialPort.StopBits = StopBits;
            serialPort.ReadTimeout = ReadTimeout;
            serialPort.ReadBufferSize = ReadBufferSize;
            serialPort.Open();
        }

        public override void Disconnect()
        {
            serialPort.Close();
            cts.Cancel();
        }

        /// <summary>
        /// 报文校验
        /// </summary>
        /// <param name="data">数据</param>
        /// <param name="length">长度</param>
        /// <returns></returns>
        protected virtual byte[] MessageCheck(byte[] data, int length) => null!;

        /// <summary>
        /// 发送并接受
        /// </summary>
        /// <param name="data">发送数据</param>
        /// <param name="receiveLength">接收长度</param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        /// <exception cref="TimeoutException"></exception>
        /// <exception cref="IOException"></exception>
        protected override byte[] SendAndReceive(byte[] data, int receiveLength)
        {
            lock (serialLock)
            {
                if (!serialPort.IsOpen)
                    throw new InvalidOperationException("串口未连接");

                serialPort.DiscardInBuffer();
                serialPort.Write(data, 0, data.Length);
                byte[] buffer = new byte[receiveLength];
                int totalLength = 0;
                var stopwatch = Stopwatch.StartNew();

                try
                {
                    while (totalLength < receiveLength)
                    {
                        if (stopwatch.ElapsedMilliseconds > serialPort.ReadTimeout)
                            throw new TimeoutException("接收数据超时");

                        try
                        {
                            int readLength = serialPort.Read(buffer, totalLength, receiveLength - totalLength);
                            totalLength += readLength;
                        }
                        catch (TimeoutException)
                        {
                            throw; //当前 Read 会超时抛异常，交由外层 catch
                        }
                        if (totalLength < receiveLength)
                            Thread.Sleep(5);
                    }
                    return buffer;
                }
                catch (TimeoutException ex)
                {
                    serialPort.DiscardInBuffer();
                    throw new TimeoutException("串口接收超时", ex);
                }
                catch (Exception ex)
                {
                    serialPort.DiscardInBuffer();
                    throw new IOException("串口通信失败", ex);
                }
            }
        }

    }
}
